// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package parser

import (
	"bytes"
	"fmt"
	"github.com/cznic/strutil"
	"go/token"
	"log"
	"path"
	"runtime"
	"strconv"
)

// me reports its calling site as a string.
func me() string {
	_, file, line, ok := runtime.Caller(1)
	if !ok {
		return "???"
	}

	return path.Base(file) + ":" + strconv.Itoa(line)
}

func internalError(args ...interface{}) {
	_, file, line, ok := runtime.Caller(1)
	if !ok {
		log.Fatalf("???: internal error")
	}

	more := ""
	for _, v := range args {
		more += fmt.Sprintf("\n%#v", v)
	}
	log.Fatalf("%s:%d: internal error %s", path.Base(file), line, more)
}

type NodeOp int

// Values of NodeOp
const (
	_ NodeOp = iota
	OADD
	OADDR
	OAND
	OANDAND
	OANDNOT
	OAS
	OAS2
	OASOP
	OBLOCK
	OBREAK
	OCALL
	OCASE
	OCLOSURE
	OCOM
	OCOMPLIT
	OCONTINUE
	ODCL
	ODCLCONST
	ODCLFIELD
	ODCLFUNC
	ODCLTYPE
	ODDD
	ODEC
	ODEFER
	ODIV
	ODOTTYPE
	OEQ
	OFALL
	OFOR
	OGE
	OGOTO
	OGT
	OIF
	OINC
	OIND
	OINDEX
	OKEY
	OLABEL
	OLE
	OLITERAL
	OLSH
	OLT
	OMINUS
	OMOD
	OMUL
	ONAME
	ONE
	ONOT
	OOR
	OOROR
	OPAREN
	OPLUS
	OPROC
	ORANGE
	ORECV
	ORETURN
	ORSH
	OSELECT
	OSEND
	OSLICE
	OSUB
	OSWITCH
	OTARRAY
	OTCHAN
	OTFUNC
	OTINTER
	OTMAP
	OTPAREN
	OTSTRUCT
	OTYPE
	OTYPESW
	OXDOT
	OXOR
	_NodeOpLimit // keep last
)

var NodeOps = map[NodeOp]string{
	OADD:      "OADD",
	OADDR:     "OADDR",
	OAND:      "OAND",
	OANDAND:   "OANDAND",
	OANDNOT:   "OANDNOT",
	OAS2:      "OAS2",
	OAS:       "OAS",
	OASOP:     "OASOP",
	OBLOCK:    "OBLOCK",
	OBREAK:    "OBREAK",
	OCALL:     "OCALL",
	OCASE:     "OCASE",
	OCLOSURE:  "OCLOSURE",
	OCOM:      "OCOM",
	OCOMPLIT:  "OCOMPLIT",
	OCONTINUE: "OCONTINUE",
	ODCL:      "ODCL",
	ODCLCONST: "ODCLCONST",
	ODCLFIELD: "ODCLFIELD",
	ODCLFUNC:  "ODCLFUNC",
	ODCLTYPE:  "ODCLTYPE",
	ODDD:      "ODDD",
	ODEC:      "ODEC",
	ODEFER:    "ODEFER",
	ODIV:      "ODIV",
	ODOTTYPE:  "ODOTTYPE",
	OEQ:       "OEQ",
	OFALL:     "OFALL",
	OFOR:      "OFOR",
	OGE:       "OGE",
	OGOTO:     "OGOTO",
	OGT:       "OGT",
	OIF:       "OIF",
	OINC:      "OINC",
	OIND:      "OIND",
	OINDEX:    "OINDEX",
	OKEY:      "OKEY",
	OLABEL:    "OLABEL",
	OLE:       "OLE",
	OLITERAL:  "OLITERAL",
	OLSH:      "OLSH",
	OLT:       "OLT",
	OMINUS:    "OMINUS",
	OMOD:      "OMOD",
	OMUL:      "OMUL",
	ONAME:     "ONAME",
	ONE:       "ONE",
	ONOT:      "ONOT",
	OOR:       "OOR",
	OOROR:     "OOROR",
	OPAREN:    "OPAREN",
	OPLUS:     "OPLUS",
	OPROC:     "OPROC",
	ORANGE:    "ORANGE",
	ORECV:     "ORECV",
	ORETURN:   "ORETURN",
	ORSH:      "ORSH",
	OSELECT:   "OSELECT",
	OSEND:     "OSEND",
	OSLICE:    "OSLICE",
	OSUB:      "OSUB",
	OSWITCH:   "OSWITCH",
	OTARRAY:   "OTARRAY",
	OTCHAN:    "OTCHAN",
	OTFUNC:    "OTFUNC",
	OTINTER:   "OTINTER",
	OTMAP:     "OTMAP",
	OTPAREN:   "OTPAREN",
	OTSTRUCT:  "OTSTRUCT",
	OTYPE:     "OTYPE",
	OTYPESW:   "OTYPESW",
	OXDOT:     "OXDOT",
	OXOR:      "OXOR",
}

type Node struct {
	Pos     token.Pos
	Op      NodeOp //
	Name    *Sym
	Literal *Val
	Type    *Type
	CType   CType
	Outer   *Node   // For closure processing
	NL      *Node   // node left
	NR      *Node   // node right
	N3      *Node   // node 3
	LL      []*Node // list left
	LR      []*Node // list right
	L3      []*Node // list 3
	AsOpTok int
	IsDDD   bool
	IsColas bool
	Iota    int
}

func (n *Node) list() (lst []*Node) {
	const alloc = 4 // Best guess (avg 2.07), speed fluctuates a lot. Why?
	// alloc: parse speed, waste (cummulative over all valid sources in the $GOROOT/src/pkg tree)
	// 1: 4.57 MB/s,    87 516(11.00%,    700 128 B on a 64b system)
	// 2: 4.68 MB/s,   319 716(31.00%,  2 557 728 B on a 64b system)
	// 3: 4.70 MB/s,   587 453(46.00%,  4 699 624 B on a 64b system)
	// 4: 4.65 MB/s,   873 346(56.00%,  6 986 768 B on a 64b system)
	// 5: 4.75 MB/s, 1 180 902(63.00%,  9 447 216 B on a 64b system)
	// 6: 5.09 MB/s, 1 492 955(68.00%, 11 943 640 B on a 64b system)
	// 7: 4,63 MB/s
	// 8: 4,57 MB/s
	// 9: 5,04 MB/s
	lst = make([]*Node, 1, alloc)
	lst[0] = n
	return
}

func (n *Node) IsBlank() bool {
	return n != nil && n.Op == ONAME && n.Name.IsBlank()
}

type TypeTag int

// Values of TypeTag
const (
	_ TypeTag = iota
	TINTER
	_TypeTagLimit // keep last
)

var TypeTags = map[TypeTag]string{
	TINTER: "TINTER",
}

type Type struct {
	Tag TypeTag
	//TODO
}

type CType int

// Values of CType
const (
	_           CType = iota
	Crecv             // 1
	Csend             // 2
	Cboth             // Crecv | Csend
	_CTypeLimit       // keep last
)

var CTypes = map[CType]string{
	Crecv: "Crecv",
	Csend: "Csend",
	Cboth: "Cboth",
}

type SymTag int

// Values of SymTag
const (
	_ SymTag = iota
	SymFunc
	SymMeth
	SymVar
	SymParam
	_SymTagLimit // keep last
)

var SymTags = map[SymTag]string{
	SymFunc:  "SymFunc",
	SymMeth:  "SymMeth",
	SymVar:   "SymVar",
	SymParam: "SymParam",
}

type Sym struct {
	Pos      token.Pos
	Tag      SymTag
	Packname []byte
	Name     []byte
	Scope    *Scope
}

func (s *Sym) IsBlank() bool {
	return s != nil && len(s.Name) == 1 && s.Name[0] == '_'
}

type ValTag int

// Values of ValTag
const (
	_       ValTag = iota
	CplxVal        // 42i
	FltVal         // 1.23e45
	IntVal         // 42
	RuneVal        // 'a'
	StrVal         // `foo` or "bar"
	BadVal
	_ValTagLimit // keep last
)

var ValTags = map[ValTag]string{
	CplxVal: "CplxVal",
	FltVal:  "FltVal",
	IntVal:  "IntVal",
	RuneVal: "RuneVal",
	StrVal:  "StrVal",
	BadVal:  "BadVal",
}

type Val struct {
	Pos token.Pos
	Tag ValTag
	Val []byte // original representation
}

func PrintAST(fset *token.FileSet, f strutil.Formatter, pre string, item interface{}) {
	switch x := item.(type) {
	default:
		//f.Format("%s: TODO %T %v\n", me(), x, x) //TODO-
		log.Fatalf("%s: TODO %T %v", me(), x, x) //TODO-
	case []*Node:
		f.Format("%s%T(len %d)%i\n", pre, x, len(x))
		for i, v := range x {
			f.Format("%d:", i)
			PrintAST(fset, f, "", v)
		}
		f.Format("%u")
	case *Type:
		f.Format("%s%T, Tag: %s(=%d)\n", pre, x, TypeTags[x.Tag], x.Tag)
	case *Val:
		f.Format("%s%T, Tag: %s(=%d), Val: %q\n", pre, x, ValTags[x.Tag], x.Tag, x.Val)
	case *Sym:
		f.Format("%s%T, Pos: %s, Tag: %s(=%d), Name: ", pre, x, fset.Position(x.Pos), SymTags[x.Tag], x.Tag)
		if x.Packname != nil {
			f.Format("%q.", x.Packname)
		}
		f.Format("%q, %s.%d\n", x.Name, ScopeTags[x.Scope.Tag], x.Scope.Depth())
	case *Node:
		if x == nil {
			f.Format("%s%T <nil>\n", pre, x)
			break
		}

		f.Format("%s%T%i, Pos: %s, Op: %s\n", pre, x, fset.Position(x.Pos), NodeOps[x.Op])
		if x.Name != nil {
			PrintAST(fset, f, "Name:", x.Name)
		}
		if x.Type != nil {
			PrintAST(fset, f, "Type:", x.Type)
		}
		if x.CType != 0 {
			f.Format("CType: %s(=%d)\n", CTypes[x.CType], x.CType)
		}
		if x.Literal != nil {
			PrintAST(fset, f, "Literal:", x.Literal)
		}
		if x.Outer != nil {
			f.Format("Outer: %s @ %s\n", NodeOps[x.Outer.Op], fset.Position(x.Outer.Pos))
		}
		if x.NL != nil {
			PrintAST(fset, f, "NL:", x.NL)
		}
		if x.NR != nil {
			PrintAST(fset, f, "NR:", x.NR)
		}
		if x.N3 != nil {
			PrintAST(fset, f, "N3:", x.N3)
		}
		if x.LL != nil {
			PrintAST(fset, f, "LL:", x.LL)
		}
		if x.LR != nil {
			PrintAST(fset, f, "LR:", x.LR)
		}
		if x.L3 != nil {
			PrintAST(fset, f, "L3:", x.L3)
		}
		if x.AsOpTok != 0 {
			f.Format("AsOpTok: '%c'\n", x.AsOpTok)
		}
		if x.IsDDD {
			f.Format("IsDDD: true\n")
		}
		if x.IsColas {
			f.Format("IsColas: true\n")
		}
		if x.Iota != 0 {
			f.Format("Iota: %d\n", x.Iota)
		}
		f.Format("%u")
	}
}

// Doesn't handle all cases, limited to parser's internal needs.
func (n *Node) String() string {
	buf := &bytes.Buffer{}
	f := strutil.IndentFormatter(buf, "")
	ns(f, n)
	return buf.String()
}

func ns(f strutil.Formatter, item interface{}) {
	switch x := item.(type) {
	default:
		internalError(fmt.Sprintf("%T %#v", x, x))
	case []*Node:
		if x == nil {
			return
		}
		ns(f, x[0])
		for _, v := range x[1:] {
			f.Format(",")
			ns(f, v)
		}
	case *Sym:
		if x.Packname != nil {
			f.Format("%s.", x.Packname)
		}
		f.Format("%s", x.Name)
	case *Node:
		if x == nil {
			return
		}
		switch x.Op {
		default:
			internalError(NodeOps[x.Op])
		case OCALL:
			ns(f, x.NL)
			f.Format("(")
			ns(f, x.LL)
			f.Format(")")
		case ONAME:
			ns(f, x.Name)
		case ORECV:
			f.Format("<-")
			ns(f, x.NL)
		case OXDOT:
			ns(f, x.NL)
			f.Format(".")
			ns(f, x.NR)
		}
	}
}
